package cn.edu.nwpu.warehousemanagement.util;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Redis工具类
 *
 * @author Loadstar
 * @version 1.0
 */
@Component
public class RedisUtil {
    @Resource
    private StringRedisTemplate template;
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private ValueOperations<String, String> operations;
    private ZSetOperations<String, String> zSetOperations;
//    private HashOperations<String, Object, Object> hashOperations;

    /**
     * 初始化
     */
    @PostConstruct
    public void init() {
        operations = template.opsForValue();
        zSetOperations = redisTemplate.opsForZSet();
//       hashOperations = template.opsForHash();
    }

    /**
     * 设置过期时间
     *
     * @param key   键
     * @param value 值
     * @param time  过期时间
     * @param unit  时间单位
     */
    public void set(String key, String value, long time, TimeUnit unit) {
        operations.set(key, value, time, unit);
    }

    /**
     * 设置过期时间,单位秒
     *
     * @param key   键
     * @param value 值
     * @param time  过期时间,单位秒
     */
    public void set(String key, String value, long time) {
        this.set(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 获取值
     *
     * @param key 键
     * @return 值 (String)
     */
    public String get(String key) {
        return operations.get(key);
    }

    /**
     * 删除键
     *
     * @param key 键
     */
    public void delete(String key) {
        operations.getOperations().delete(key);
//        template.delete(key);
    }

    public void putJWT(Long userId, String token, long time, TimeUnit unit) {
        this.set("token:{" + token + '}', userId.toString(), time, unit);
        long expireTimestamp = System.currentTimeMillis() + unit.toMillis(time);
        zSetOperations.add("IdTokens:" + userId, token, expireTimestamp);
    }

    public String getJWT(String token) {
        return this.get("token:{" + token + '}');
    }


    public void deleteCurrentJWT(String token, Long userId) {
        this.delete("token:{" + token + '}');
        zSetOperations.remove("IdTokens:" + userId, token);
    }

    /**
     * 批量删除所有与指定用户 ID 相关的 JWT 令牌
     *
     * @param userId 用户 ID
     */
    public void deleteAllJWTsById(Long userId) {
        try {
            String zSetKey = "IdTokens:" + userId;
            Set<String> tokens = zSetOperations.range(zSetKey, 0, -1);

            // 批量删除所有 Token 的字符串键,而不是读一个删一个
            assert tokens != null;
            if (!tokens.isEmpty()) {
                List<String> tokenKeys = tokens.stream()
                        .map(token -> "token:{" + token + "}")
                        .collect(Collectors.toList());
                redisTemplate.delete(tokenKeys);
            }

            // 删除 zSet
            redisTemplate.delete(zSetKey);
        } catch (Exception e) {
            // 记录日志并处理异常
            LogUtil.error(RedisUtil.class, "Failed to delete tokens for user: {%d}".formatted(userId), e);
        }
    }

    /**
     * 批量删除所有与指定用户 ID 相关的 JWT 令牌
     * 用Lua脚本实现，保证原子性
     *
     * @param userId 用户 ID
     */
    public void deleteAllJWTsByIdWithLua(Long userId) {
        try {
            String script = """
                    local zSetKey = KEYS[1]
                    local tokens = redis.call('ZRANGE', zSetKey, 0, -1)
                    redis.call('DEL', unpack(tokens))
                    redis.call('DEL', zSetKey)
                    return #tokens""";

            RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
            List<String> keys = Collections.singletonList("IdTokens:" + userId);
            redisTemplate.execute(redisScript, keys);
        } catch (Exception e) {
            LogUtil.error(RedisUtil.class, "Failed to delete tokens for user: {%d}".formatted(userId), e);

        }
    }

    public void cleanExpiredTokens(String userId, long now) {
        String script =
                """
                        local key = KEYS[1]
                        local now = tonumber(ARGV[1])
                        redis.call('ZREMRANGEBYSCORE', key, 0, now)
                        return redis.call('ZCARD', key)""";

        // 将 Lua 脚本封装为 RedisScript 对象
        RedisScript<Long> redisScript = RedisScript.of(script, Long.class);

        // 构造参数
        List<String> keys = Collections.singletonList("IdTokens:" + userId);
        List<String> args = Collections.singletonList(String.valueOf(now));

        // 执行 Lua 脚本
        Long remainingCount = redisTemplate.execute(redisScript, keys, args);
        LogUtil.info(RedisUtil.class, "Cleaned expired tokens for user " + userId + ", remaining: " + remainingCount);
    }


    /**
     * 每小时扫描并清理过期的令牌
     *  未测试 todo
     **/
    @Scheduled(fixedRate = 1000 * 60 * 60)
    public void scanAndCleanAllUsersJWT() {
        long now = System.currentTimeMillis();
        ScanOptions options = ScanOptions.scanOptions().match("IdTokens:*").build();
        //键扫描方法
        try (Cursor<String> cursor = redisTemplate.scan(options)) {
            while (cursor.hasNext()) {
                String key = new String(cursor.next().getBytes(), StandardCharsets.UTF_8);
                cleanExpiredTokens(key, now);
            }
        } catch (Exception e) {
            throw new RuntimeException("Scan failed", e);
        }
    }

    /**
     * @Deprecated use scanAndCleanAllUsersJWT instead
     **/
//    @Scheduled(fixedRate = 1000 * 60 * 60)
    public void scanAndCleanAllUsers() {
        long now = System.currentTimeMillis();
        ScanOptions options = ScanOptions.scanOptions().match("IdTokens:*").build();

        // 使用 RedisTemplate 的连接对象执行 SCAN
        RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection();
        Cursor<byte[]> cursor = connection.scan(options);

        while (cursor.hasNext()) {
            byte[] keyBytes = cursor.next();
            String key = new String(keyBytes);
            cleanExpiredTokens(key, now); // 调用上面定义的清理方法
        }
    }
}
